package com.gadarts.parashoot;

import java.util.ArrayList;
import java.util.Random;

import android.graphics.Bitmap;
import android.graphics.Matrix;

import com.gadarts.parashoot.utils.C;
import com.gadarts.parashoot.utils.Utils;
import com.gadarts.parashoot.utils.ViewUtils;

public abstract class GameObject {

	protected GameModel model;
	protected GameView view;
	protected int x, y, speed, direction, rotateChange, currentFrame, depth,
			pivotX, pivotY;
	protected Matrix matrix = new Matrix();// Used for rotating.

	protected ArrayList<Sprite> sprites;// Multiple-sprites object.
	protected Sprite sprite;// One-sprite object.

	protected Object child;// This class's child object.
	protected float gravity;
	protected boolean gravityStatus;
	protected Random random = new Random();
	protected Bitmap currentImage;
	private boolean initialized;

	private int animationSpeed;
	private String animationTimer;// The name of the animation timer.

	// Whether it has an animation; Whether the animation needs to loop.
	protected boolean runningAnimation, loop;

	// Whether to destroy object when animation is finished.
	private boolean destroyAtEnd;

	// Used to check if the object was created outside the room.
	private boolean dontDestroyYet;

	// Whether to draw the current frame in the current direction.
	private boolean fitFrameToDirection = true;

	public GameObject(GameModel model, GameView view,
			ArrayList<Sprite> sprites, int x, int y, int speed, int direction,
			int depth, boolean fitFrameToDirection) {
		this.model = model;
		this.view = view;
		this.sprites = sprites;
		sprite = sprites.get(0);
		this.x = x;
		this.y = y;
		this.speed = speed;
		this.direction = direction;
		this.depth = depth;
		this.fitFrameToDirection = fitFrameToDirection;
	}

	public GameObject(GameModel model, GameView view, Bitmap image, int x,
			int y, int speed, int direction, int depth,
			boolean fitFrameToDirection) {
		Sprite sprite = new Sprite();
		sprite.add(image);
		this.model = model;
		this.view = view;
		this.sprite = sprite;
		this.x = x;
		this.y = y;
		this.speed = speed;
		this.direction = direction;
		this.depth = depth;
		this.fitFrameToDirection = fitFrameToDirection;
	}

	public GameObject(GameModel model, GameView view, Sprite sprite, int x,
			int y, int speed, int direction, int depth,
			boolean fitFrameToDirection) {
		this.model = model;
		this.view = view;
		this.sprite = sprite;
		this.x = x;
		this.y = y;
		this.speed = speed;
		this.direction = direction;
		this.depth = depth;
		this.fitFrameToDirection = fitFrameToDirection;
	}

	// Whatever happens on the first update.
	private void createEvent() {
		if (x + getWidth() < 0 || x > view.getWidth() || y + getHeight() < 0
				|| y > view.getHeight())
			dontDestroyYet = true;
		initialized = true;
	}

	public void update() {
		if (!initialized)// Perform create event once.
			createEvent();
		if (rotateChange != 0)// Rotate if needed.
			setDirection(direction + rotateChange);
		try {
			currentImage = sprite.get(currentFrame);
		} catch (Exception e) {
			currentImage = sprite.get(0);
		}
		checkOutside();// Destroy the object if it is outside the view.
		positionAndRotate();// Fits object to current position and direction.
		move();// Move the object if it has speed.
		runAnimation();
	}

	// Fits the object to it's current position and direction.
	protected void positionAndRotate() {
		matrix.setTranslate(x, y);
		matrix.preRotate(direction, pivotX, pivotY);
	}

	// Move the object if it has speed.
	protected void move() {
		if (speed > 0) {
			setX(x + ((int) (Math.cos(Math.toRadians(direction)) * speed)));

			int newY = y
					+ ((int) (Math.sin(Math.toRadians(direction)) * speed));
			int groundY = model.getGroundY();
			if (newY < groundY)
				setY(newY);
			else
				// setY(groundY - getHeight());
				setY(groundY);
		}
		if (gravityStatus) {// Fall.
			y += gravity;
			if (gravity < C.InGame.GRAVITY)
				gravity += C.InGame.GRAVITY_ACCELERATION;
		}
	}

	protected void resetAnimation(int animationSpeed) {
		resetAnimation(animationSpeed, false);
	}
	
	protected void resetAnimation(int animationSpeed, boolean loop) {
		this.animationSpeed = animationSpeed;
		currentFrame = 0;
		currentImage = sprite.getFirstFrame();
		runningAnimation = true;
		this.loop = loop;
	}

	// Run the animation with given parameters.
	private void runAnimation() {
		if (runningAnimation && animationTimer != null)// Flip through frames.
			if (Utils.checkTiming(model, animationTimer, animationSpeed)) {

				currentFrame = ViewUtils.animate(sprite, currentFrame, loop);

				if (currentFrame == sprite.size() - 1) {
					if (destroyAtEnd)
						model.destroyObject(this, child.getClass());
				}

			}
	}

	protected void setAnimation(int speed, String timerName,
			boolean destroyAtEnd) {
		setAnimation(speed, timerName, destroyAtEnd, true);
	}

	protected void setAnimation(int speed, String timerName,
			boolean destroyAtEnd, boolean loop) {
		runningAnimation = true;
		animationSpeed = speed;
		animationTimer = Utils.setTimer(model, timerName);
		this.destroyAtEnd = destroyAtEnd;
		this.loop = loop;
	}

	public Bitmap getImage() {
		return sprite.get(currentFrame);
	}

	public Sprite getSprite() {
		return sprite;
	}

	public int getX() {
		return x;
	}

	public int getY() {
		return y;
	}

	public int getCenterX() {
		return x + getImage().getWidth() / 2;
	}

	public int getCenterY() {
		return y + getImage().getHeight() / 2;
	}

	public int getRight() {
		return x + getImage().getWidth();
	}

	public int getBottom() {
		return y + getImage().getHeight();
	}

	public int getSpeed() {
		return speed;
	}

	public int getWidth() {
		return getImage().getWidth();
	}

	public int getHeight() {
		return getImage().getHeight();
	}

	public int getDirection() {
		return direction;
	}

	protected Matrix getMatrix() {
		return matrix;
	}

	public int getDepth() {
		return depth;
	}

	protected void setDirection(int direction) {
		while (direction >= 360)
			// Direction needs to be in degrees.
			direction -= 360;
		while (direction < 0)
			direction += 360;
		this.direction = direction;
		matrix.setRotate(direction, pivotX, pivotY);
	}

	protected void setRotation(int change) {
		rotateChange = change;
	}

	protected void setX(int x) {
		this.x = x;
	}

	protected void setY(int y) {
		this.y = y;
	}

	public void setChild(Object child) {
		this.child = child;
	}

	// Destroy the object if it is outside the view.
	private void checkOutside() {
		if (x + getWidth() < 0 || x > view.getWidth() || y + getHeight() < 0
				|| y > view.getHeight()) {
			if (dontDestroyYet == false)
				model.destroyObject(this, child.getClass());
		} else if (x > 0 && x + getWidth() < view.getWidth() && y > 0
				&& y + getHeight() < view.getHeight())
			dontDestroyYet = false;
	}

	// Returns whether the object's touching the ground.
	protected boolean isTouchingGround() {
		if (y + getHeight() >= model.getGroundY())// Collision with ground.
			return true;
		return false;
	}

	// Returns whether to draw the current frame in the current direction.
	public boolean fitFrameToDirection() {
		return fitFrameToDirection;
	}

	public void setSprite(Sprite sprite) {
		this.sprite = sprite;
		currentFrame = 0;
		currentImage = sprite.get(currentFrame);
	}
}
